package com.nidu.demo.auth;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.alibaba.cola.exception.BizException;
import com.nidu.demo.common.enums.BooleanEnum;
import com.nidu.demo.common.exception.ErrorCodeConstants;
import com.nidu.demo.oauth2.dto.OAuth2ApproveListQry;
import com.nidu.demo.common.enums.UserTypeEnum;
import com.nidu.demo.oauth2.ability.*;
import com.nidu.demo.auth.api.OAuth2Api;
import com.nidu.demo.oauth2.convertor.OAuth2AccessTokenConvertor;
import com.nidu.demo.oauth2.dto.OAuth2AccessTokenCO;
import com.nidu.demo.oauth2.dto.OAuth2ApproveCO;
import com.nidu.demo.oauth2.dto.OAuth2AuthorizeCmd;
import com.nidu.demo.oauth2.dto.OAuth2GrantCmd;
import com.nidu.demo.oauth2.gateway.OAuth2AccessTokenGateway;
import com.nidu.demo.oauth2.gateway.OAuth2ApproveGateway;
import com.nidu.demo.oauth2.gateway.OAuth2CodeGateway;
import com.nidu.demo.oauth2.gateway.OAuth2RefreshTokenGateway;
import com.nidu.demo.oauth2.model.*;
import com.nidu.demo.security.util.SecurityUtils;
import com.nidu.demo.user.ability.UserDomainService;
import com.nidu.demo.user.model.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.nidu.demo.common.enums.OAuth2GrantTypeEnum;
import com.nidu.demo.oauth2.util.OAuth2Utils;

@Service
@RequiredArgsConstructor
public class OAuth2ApiImpl implements OAuth2Api {

    private final OAuth2AccessTokenConvertor accessTokenConvertor;

    private final OAuth2ClientDomainService clientDomainService;

    private final OAuth2CodeDomainService codeDomainService;

    private final OAuth2AccessTokenDomainService accessTokenDomainService;

    private final OAuth2RefreshTokenDomainService refreshTokenDomainService;

    private final OAuth2ApproveDomainService approveDomainService;

    private final UserDomainService userDomainService;

    private final OAuth2ApproveGateway approveGateway;

    private final OAuth2CodeGateway codeGateway;

    @Override
    @Transactional
    public SingleResponse<String> authorize(OAuth2AuthorizeCmd authorizeCmd) {
        // 校验客户端
        OAuth2Client client = clientDomainService.validateClient(authorizeCmd.getClientCode());

        // 校验回调URI是否匹配
        client.validateRedirectUri(authorizeCmd.getRedirectUri());
        
        // 校验授权范围
        client.validateScopes(authorizeCmd.getScopes());
        
        // 如果用户同意授权，保存授权记录
        if (Boolean.TRUE.equals(authorizeCmd.getApproved())) {
            approveDomainService.createApprove(authorizeCmd.getUserId(), UserTypeEnum.MEMBER.getType(), client, authorizeCmd.getScopes());
        }

        // 根据不同的grantType处理
        String grantType = authorizeCmd.getGrantType();
        OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.of(grantType);
        switch (grantTypeEnum) {
            case AUTHORIZATION_CODE:
                OAuth2Code oAuth2Code = codeDomainService.createCode(authorizeCmd.getUserId(), UserTypeEnum.MEMBER.getType(), client, authorizeCmd.getScopes(), authorizeCmd.getRedirectUri());
                return SingleResponse.of(OAuth2Utils.buildAuthorizationCodeRedirectUrl(authorizeCmd.getRedirectUri(),
                        oAuth2Code.getAuthorizationCode(), authorizeCmd.getState()));
            case IMPLICIT:
                OAuth2AccessToken accessToken = accessTokenDomainService.createToken(authorizeCmd.getUserId(),
                        UserTypeEnum.MEMBER.getType(), client.getId(), authorizeCmd.getScopes(), client.getRefreshTokenValiditySeconds(), client.getAccessTokenValiditySeconds());
                return SingleResponse.of(OAuth2Utils.buildImplicitRedirectUrl(authorizeCmd.getRedirectUri(), accessToken, authorizeCmd.getState()));
            default:
                throw new BizException("只支持授权码和简单授权类型");
        }
    }

    @Override
    public SingleResponse<OAuth2ApproveCO> getAuthorize(String clientCode, String clientSecret) {
        // 校验客户端
        OAuth2Client client = clientDomainService.validateClient(clientCode);
        client.validateClientSecret(clientSecret);

        // 获取当前用户的授权记录
        OAuth2ApproveCriteria listQry = new OAuth2ApproveCriteria();
        listQry.setUserId(SecurityUtils.getLoginUserId());
        listQry.setClientId(client.getId());
        List<OAuth2Approve> oAuth2Approves = approveGateway.listByCondition(listQry);
        if(CollUtil.isEmpty(oAuth2Approves)){
            return SingleResponse.of(null);
        }
        // 移除已过期的授权
        oAuth2Approves.removeIf(OAuth2Approve::isExpired);

        // 构建返回对象
        OAuth2ApproveCO approveCO = new OAuth2ApproveCO();
        if (!oAuth2Approves.isEmpty()) {
            // 如果有未过期的授权记录，使用最新的一条
            OAuth2Approve latestApprove = oAuth2Approves.get(0);
            approveCO.setUserId(latestApprove.getUserId());
            approveCO.setUserType(latestApprove.getUserType());
            approveCO.setClientId(latestApprove.getClientId());
            approveCO.setScopes(latestApprove.getScopes());
            approveCO.setExpiresTime(latestApprove.getExpiresTime());
            approveCO.setApproved(true);  // 有未过期授权记录，表示已同意授权
        } else {
            // 没有有效的授权记录
            approveCO.setApproved(false);
        }
        return SingleResponse.of(approveCO);
    }

    @Override
    @Transactional
    public SingleResponse<OAuth2AccessTokenCO> grantToken(OAuth2GrantCmd cmd) {
        OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.of(cmd.getGrantType());
        OAuth2AccessToken accessToken = null;
        OAuth2Client client = clientDomainService.validateClient(cmd.getClientCode());
        client.validateClientSecret(cmd.getClientSecret());
        // 校验授权范围
        client.validateScopes(cmd.getScopes());

        switch (grantTypeEnum){
            case AUTHORIZATION_CODE:
                // 授权码模式
                // 验证授权码的有效性
                OAuth2Code code = codeDomainService.validateCode(cmd.getCode(), client.getId(), cmd.getRedirectUri());
                // 删除授权码，确保一次性使用
                codeGateway.deleteById(code.getId());
                // 创建访问令牌
                accessToken = accessTokenDomainService.createToken(code.getUserId(),  UserTypeEnum.MEMBER.getType(), client.getId(), cmd.getScopes(), client.getRefreshTokenValiditySeconds(), client.getAccessTokenValiditySeconds());
                break;
            case PASSWORD:
                // 密码模式
                User user = userDomainService.authenticate(cmd.getUsername(), cmd.getPassword());
                accessToken = accessTokenDomainService.createToken(user.getId(),  UserTypeEnum.MEMBER.getType(), client.getId(), cmd.getScopes(), client.getRefreshTokenValiditySeconds(), client.getAccessTokenValiditySeconds());
                break;
            case CLIENT_CREDENTIALS:
                // TODO 客户端模式
                throw new BizException("客户端模式暂时不支持");
            case REFRESH_TOKEN:
                // 验证刷新令牌
                OAuth2RefreshToken refreshToken = refreshTokenDomainService.validateRefreshToken(cmd.getRefreshToken(), client.getId());
                // 刷新访问令牌
                accessToken = accessTokenDomainService.refreshAccessToken(SecurityUtils.getLoginUserId(), UserTypeEnum.MEMBER.getType(), refreshToken.getRefreshToken(), client.getId(), refreshToken.getScopes(), client.getAccessTokenValiditySeconds());
                break;
            default:
                break;
        }
        OAuth2AccessTokenCO accessTokenCO = accessTokenConvertor.toClientObject(accessToken);
        return SingleResponse.of(accessTokenCO);
    }

    @Override
    public SingleResponse<OAuth2AccessTokenCO> checkAccessToken(String clientId, String clientSecret, String accessToken) {
        OAuth2Client client = clientDomainService.validateClient(clientId);
        client.validateClientSecret(clientSecret);
        OAuth2AccessToken auth2AccessToken = accessTokenDomainService.validateAccessToken(accessToken);
        return SingleResponse.of(accessTokenConvertor.toClientObject(auth2AccessToken));
    }

    @Override
    public Response removeAccessToken(String clientId, String clientSecret, String accessToken) {
        OAuth2Client client = clientDomainService.validateClient(clientId);
        client.validateClientSecret(clientSecret);
        accessTokenDomainService.removeAccessToken(accessToken);
        return Response.buildSuccess();
    }

}